package com.oig.sys.listener;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.oig.common.constants.CommonConstants;
import com.oig.common.constants.CommonEnum;
import com.oig.common.util.JsonUtil;
import com.oig.common.util.SpringContextUtil;
import com.oig.sys.config.JpaAuditProperties;
import com.oig.sys.log.dto.SysAuditLogDto;
import com.oig.sys.log.event.SysAuditLogEvent;
import com.oig.sys.util.DataCompareUtil;
import com.oig.sys.workflow.dto.AuditCallMsgDto;
import com.oig.sys.workflow.event.AuditCallEvent;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.event.spi.PostCommitDeleteEventListener;
import org.hibernate.event.spi.PostCommitInsertEventListener;
import org.hibernate.event.spi.PostCommitUpdateEventListener;
import org.hibernate.event.spi.PostDeleteEvent;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.SingleTableEntityPersister;
import org.slf4j.MDC;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class JpaAuditListener implements PostCommitDeleteEventListener, PostCommitInsertEventListener, PostCommitUpdateEventListener {


    private final JpaAuditProperties jpaAuditProperties;

    public JpaAuditListener(JpaAuditProperties jpaAuditProperties) {
        log.debug("init jpa audit log listener:{}", jpaAuditProperties);
        this.jpaAuditProperties = jpaAuditProperties;
    }

    @Override
    public void onPostDelete(PostDeleteEvent event) {
        final SingleTableEntityPersister entityPersister = (SingleTableEntityPersister) event.getPersister();
        final String tableName = entityPersister.getTableName();
        // 只有配置了需要监听的表才记录审计日志
        if (jpaAuditProperties.isAudit(tableName)) {
            Map<String,Object> oldValue = new HashMap<>();
            DataCompareUtil.compareDiff(event.getDeletedState(),event.getPersister().getPropertyNames(),event.getPersister().getPropertyTypes(),oldValue);
            if (jpaAuditProperties.getLogEnabled()){
                log.debug("delete audit log");
                this.pushToAddAuditLog(this.buildAuditLogDto(tableName, event.getId(), CommonEnum.OperationEnum.delete,null,oldValue));
            }
            if (jpaAuditProperties.getAutoCallEnabled()){
                log.debug("delete auto call");
                this.pushToAuditCall(this.buildAuditCallMsgDto(event.getEntity(),tableName, event.getId(), CommonEnum.OperationEnum.delete,null,null));
            }
        }
    }

    @Override
    public void onPostInsert(PostInsertEvent event) {
        final SingleTableEntityPersister entityPersister = (SingleTableEntityPersister) event.getPersister();
        final String tableName = entityPersister.getTableName();
        // 只有配置了需要监听的表才记录审计日志
        if (jpaAuditProperties.isAudit(tableName)) {
            Map<String,Object> newValue = new HashMap<>();

            DataCompareUtil.compareDiff(event.getState(),event.getPersister().getPropertyNames(),event.getPersister().getPropertyTypes(),newValue);
            if (jpaAuditProperties.getLogEnabled()) {
                log.debug("insert audit log");
                this.pushToAddAuditLog(this.buildAuditLogDto(tableName, event.getId(), CommonEnum.OperationEnum.insert, newValue, null));
            }
            if (jpaAuditProperties.getAutoCallEnabled()){
                log.debug("insert auto call");
                this.pushToAuditCall(this.buildAuditCallMsgDto(event.getEntity(),tableName, event.getId(), CommonEnum.OperationEnum.insert,null,null));
            }
        }
    }

    @Override
    public void onPostUpdate(PostUpdateEvent event) {
        log.debug("post update");
        final SingleTableEntityPersister entityPersister = (SingleTableEntityPersister) event.getPersister();
        final String tableName = entityPersister.getTableName();
        // 只有配置了需要监听的表才记录审计日志
        if (jpaAuditProperties.isAudit(tableName)) {
            Map<String,Object> oldValue = new HashMap<>();
            Map<String,Object> newValue = new HashMap<>();
            DataCompareUtil.compareDiff(event.getState(), event.getOldState(),
                    event.getPersister().getPropertyNames(),event.getPersister().getPropertyTypes(),newValue,oldValue);
            //可以考虑直接丢mq消息，记调用msg服务写入待发消息，发送消息，再其他服务去处理收到审计消息，如log服务写日志，workflow服务做自动化处理
            if (jpaAuditProperties.getLogEnabled()) {
                log.debug("update audit log");
                this.pushToAddAuditLog(this.buildAuditLogDto(tableName, event.getId(), CommonEnum.OperationEnum.update,newValue,oldValue));
            }
            if (jpaAuditProperties.getAutoCallEnabled()){
                log.debug("update auto call");
                if (!newValue.containsKey("lastLoginTime")){    //登录就不发了
                    this.pushToAuditCall(this.buildAuditCallMsgDto(event.getEntity(),tableName, event.getId(), CommonEnum.OperationEnum.update,newValue,oldValue));
                }
            }
        }
    }


    private SysAuditLogDto buildAuditLogDto(String tableName, Serializable id,
                                            CommonEnum.OperationEnum operationEnum,
                                            Map<String,Object> newValue, Map<String,Object> oldValue){
        SysAuditLogDto auditLogDto = new SysAuditLogDto();
        auditLogDto.setTraceId(MDC.get(CommonConstants.TRACE_ID));
        auditLogDto.setTableName(tableName);
        auditLogDto.setTableId(JsonUtil.toJSONString(id));
        auditLogDto.setNewValue(newValue==null?"":JsonUtil.toJSONString(newValue));
        auditLogDto.setOldValue(oldValue==null?"":JsonUtil.toJSONString(oldValue));
        auditLogDto.setOperation(operationEnum.name());
        auditLogDto.setCreateTime(new Date());
        return auditLogDto;
    }

    private void pushToAddAuditLog(SysAuditLogDto auditLogDto){
        log.debug("推送审计日志，{}",auditLogDto);
        SpringContextUtil.publishEvent(new SysAuditLogEvent(auditLogDto));
    }


    private AuditCallMsgDto buildAuditCallMsgDto(Object entity,String tableName, Serializable id,
                                                 CommonEnum.OperationEnum operationEnum,
                                                 Map<String,Object> newValue, Map<String,Object> oldValue){
        AuditCallMsgDto auditCallMsgDto = new AuditCallMsgDto();
        auditCallMsgDto.setTableName(tableName);
        auditCallMsgDto.setTableId(JsonUtil.toJSONString(id));
        auditCallMsgDto.setOperation(operationEnum.name());
        auditCallMsgDto.setOldValue(oldValue==null?"":JsonUtil.toJSONString(oldValue));
        auditCallMsgDto.setNewValue(newValue==null?"":JsonUtil.toJSONString(newValue));
        auditCallMsgDto.setObjJson(JsonUtil.toJSONString(entity));
        //只能取数据里的 所属租户名称
        JSONObject jsonObject = JSON.parseObject(auditCallMsgDto.getObjJson());
        Integer tenantId = jsonObject.getInteger("tenantId") ;  //需要约定所有业务表都要加所属租户，并且字段名是统一的
        String tenantName = jsonObject.getString("tenantName") ;
        auditCallMsgDto.setTenantId(tenantId);
        auditCallMsgDto.setTenantName(tenantName);
        return auditCallMsgDto;
    }

    private void pushToAuditCall(AuditCallMsgDto auditCallMsgDto){
        if (auditCallMsgDto.getTenantId()!=null){ //数据如果没有所属租户，推送也没有用，这个数据找不到对应的规则，先不处理。
//            log.debug("推送审计消息，{}--token:{}", auditCallMsgDto, RequestHeaderUtil.getToken());    //通过爬虫抓取然后更新数据的，可能没有token
            log.debug("推送审计消息，{}", auditCallMsgDto);
            SpringContextUtil.publishEvent(new AuditCallEvent(auditCallMsgDto));
        }
    }

    @Deprecated
    @Override
    public boolean requiresPostCommitHanding(EntityPersister entityPersister) {
        return true;
    }

    @Override
    public void onPostDeleteCommitFailed(PostDeleteEvent postDeleteEvent) {
    }
    @Override
    public void onPostInsertCommitFailed(PostInsertEvent postInsertEvent) {
    }
    @Override
    public void onPostUpdateCommitFailed(PostUpdateEvent postUpdateEvent) {
    }

}
